﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using NSubstitute;
using Phoenix.Frames;
using Phoenix.Framework;
using Phoenix.Lifetime;
using Phoenix.Tests.Navigation;
using Xunit;

namespace Phoenix.Tests.Frames
{
    public class NavigationFrameControllerTests
    {
        readonly INavigationFrameControl navigationFrame;
        readonly NavigationFrameController subject;

        public NavigationFrameControllerTests()
        {
            navigationFrame = Substitute.For<INavigationFrameControl>();
            var phoenixHost = Substitute.For<IPhoenixHost>();
            phoenixHost.AppDispatcher.Returns(Dispatcher.CurrentDispatcher);
            subject = new NavigationFrameController(navigationFrame);
            subject.InitialiseHost(phoenixHost);
        }

        [Fact]
        public void begin_foreground_operation_throws_when_an_existing_foreground_operation_is_running()
        {
            var operation = new InvokeActionRequest(null, null, null, InvokeOptions.None);
            subject.BeginOperation(operation);

            Assert.Throws<InvalidOperationException>(() => subject.BeginOperation(operation));
        }

        [Fact]
        public void navigate_back_restores_previous_view()
        {
            // arrange
            var view = new UserControl
            {
                DataContext = Substitute.For<ViewModelBase>()
            };
            var controllerAction = new ControllerAction("", "", null);
            subject.NavigateTo(new NavigationRequest(controllerAction, null, view, new RequestTransientStore(), InvokeOptions.None));
            subject.NavigateTo(new NavigationRequest(controllerAction, null, new UserControl(), new RequestTransientStore(), InvokeOptions.None));

            // act
            subject.NavigateBack();

            // assert
            Assert.Equal(view, navigationFrame.Content);
        }

        [Fact]
        public void navigationframecontroller_disables_content_when_beginning_foreground_operation()
        {
            // arrange
            var operation = new InvokeActionRequest(null, null, null, InvokeOptions.None);

            // act
            subject.BeginOperation(operation);

            // assert
            Assert.False(navigationFrame.IsContentEnabled);
        }

        [Fact]
        public void navigationframecontroller_reenables_content_when_end_foreground_operation()
        {
            // arrange
            var operation = new InvokeActionRequest(null, null, null, InvokeOptions.None);
            subject.BeginOperation(operation);

            // act
            subject.EndOperation(operation);

            // assert
            Assert.True(navigationFrame.IsContentEnabled);
        }

        [Fact]
        public void navigationframecontroller_enables_progress_when_beginning_foreground_operation()
        {
            // arrange
            var operation = new InvokeActionRequest(null, null, null, InvokeOptions.None);

            // act
            subject.BeginOperation(operation);

            // assert
            Assert.Equal(Visibility.Visible, navigationFrame.ProgressBarVisibility);
            Assert.True(navigationFrame.ShowProgressBar);
        }

        [Fact]
        public void navigationframecontroller_disables_progress_when_end_foreground_operation()
        {
            // arrange
            var operation = new InvokeActionRequest(null, null, null, InvokeOptions.None);
            subject.BeginOperation(operation);

            // act
            subject.EndOperation(operation);

            // assert
            Assert.Equal(Visibility.Collapsed, navigationFrame.ProgressBarVisibility);
            Assert.False(navigationFrame.ShowProgressBar);
        }

        [Fact]
        public void navigationframecontroller_clears_loading_message_when_end_foreground_operation()
        {
            // arrange
            var operation = new InvokeActionRequest(null, null, null, InvokeOptions.None);
            subject.BeginOperation(operation);
            navigationFrame.LoadingMessage = "Testing!";

            // act
            subject.EndOperation(operation);

            // assert
            Assert.Null(navigationFrame.LoadingMessage);
        }

        //[Fact]
        //public void navigationframecontroller_shows_progressindicator_when_background_operation_started()
        //{
        //    // arrange
        //    // act
        //    subject.BeginBackgroundOperation();

        //    // assert
        //    navigationFrame.Received().ProgressIndicatorVisible(true);
        //}

        //[Fact]
        //public void navigationframecontroller_does_not_get_told_to_hide_progress_indicator_if_background_operation_still_running()
        //{
        //    // arrange
        //    // act
        //    subject.BeginBackgroundOperation();
        //    subject.BeginBackgroundOperation();
        //    subject.EndBackgroundOperation();

        //    // assert
        //    navigationFrame.DidNotReceive().ProgressIndicatorVisible(false);
        //}

        //[Fact]
        //public void navigationframecontroller_hides_progress_indicator_when_equal_number_beginend_pairs_called()
        //{
        //    // arrange
        //    // act
        //    subject.BeginBackgroundOperation();
        //    subject.BeginBackgroundOperation();
        //    subject.EndBackgroundOperation();
        //    subject.EndBackgroundOperation();

        //    // assert
        //    navigationFrame.Received().ProgressIndicatorVisible(false);
        //}

        [Fact]
        public void navigationframecontroller_disposes_current_children_on_navigate_back()
        {
            // arrange
            var childFrame = Substitute.For<IContentFrame>();
            var view = new UserControl
            {
                DataContext = Substitute.For<ViewModelBase>()
            };
            var controllerAction = new ControllerAction("", "", null);
            subject.NavigateTo(new NavigationRequest(controllerAction, null, view, new RequestTransientStore(), InvokeOptions.None));
            subject.NavigateTo(new NavigationRequest(controllerAction, null, new UserControl(), new RequestTransientStore(), InvokeOptions.None));
            subject.RegisterChild(childFrame);

            // act
            subject.NavigateBack();

            // assert
            childFrame.Received().Dispose();
        }

        [Fact]
        public void navigationframecontroller_does_not_deactivate_children_from_requests_on_backstack_on_deactivate_currentrequest()
        {
            // arrange
            var firstChild = Substitute.For<IContentFrame>();
            var childFrame = Substitute.For<IContentFrame>();
            var view = new UserControl { DataContext = Substitute.For<ViewModelBase>() };
            var controllerAction = new ControllerAction("Controller", "Action1", null);
            subject.NavigateTo(new NavigationRequest(controllerAction, null, view, new RequestTransientStore(), InvokeOptions.None));
            subject.RegisterChild(firstChild);
            var action = new ControllerAction("Controller", "Action2", null);
            subject.NavigateTo(new NavigationRequest(action, null, new UserControl(), new RequestTransientStore(), InvokeOptions.None));
            subject.RegisterChild(childFrame);

            // act
            subject.DeactivateCurrentRequest();

            // assert
            firstChild.DidNotReceive().Dispose();
        }

        [Fact]
        public void navigationframecontroller_disposes_all_children_from_requests_on_backstack_on_dispose()
        {
            // arrange
            var firstChild = Substitute.For<IContentFrame>();
            var childFrame = Substitute.For<IContentFrame>();
            var view = new UserControl { DataContext = Substitute.For<ViewModelBase>() };
            var controllerAction = new ControllerAction("Controller", "Action1", null);
            subject.NavigateTo(new NavigationRequest(controllerAction, null, view, new RequestTransientStore(), InvokeOptions.None));
            subject.RegisterChild(firstChild);
            var action = new ControllerAction("Controller", "Action2", null);
            subject.NavigateTo(new NavigationRequest(action, null, new UserControl(), new RequestTransientStore(), InvokeOptions.None));
            subject.RegisterChild(childFrame);

            // act
            subject.Dispose();

            // assert
            firstChild.Received().Dispose();
        }

        [Fact]
        public void frames_notified_before_navigateback_to_give_chance_to_cancel()
        {
            // arrange
            subject.Navigate(new TestViewModel());
            var viewModel = new CancelOnDisposingViewModel();
            subject.Navigate(viewModel);

            // act
            subject.NavigateBack();

            // assert
            Assert.Equal(viewModel, subject.CurrentRequest.View.DataContext);
        }

        [Fact]
        public void frames_notified_before_navigatebackto_to_give_chance_to_cancel()
        {
            // arrange
            var controllerAction = new ControllerAction("Back", "To", null);
            subject.Navigate(new TestViewModel(), controllerAction);
            subject.Navigate(new TestViewModel());
            var viewModel = new CancelOnDisposingViewModel();
            subject.Navigate(viewModel);

            // act
            subject.NavigateBackTo("Back", "To");

            // assert
            Assert.Equal(viewModel, subject.CurrentRequest.View.DataContext);
            Assert.NotEqual(controllerAction, subject.Journal.Pop().ControllerAction);
        }
    }
}